/*
 * SIT_ListBox.c : multi-column list, handled through listview control,
 *                 since plain listbox does not offer much features.
 *
 * Written by T.Pierron, Feb 2007.
 */

#include "SIT_P.h"
#include "graphics_P.h"
#include <stdio.h>
#include <stdarg.h>
#include <ctype.h>

	TagList ListBoxClass[] = {
		{ SIT_SuperClass,    NULL,            ___, SIT_INT,  (ULONG) WidgetClass },
		{ SIT_ListBoxFlags,  "listBoxFlags",  __G, SIT_INT,  OFFSET(SIT_ListBox, sl_Flags) },
		{ SIT_ColumnCount,   "columnCount",   __G, SIT_INT,  OFFSET(SIT_ListBox, sl_ColumnCount) },
		{ SIT_ColumnWidths,  "columnWidths",  _SG, SIT_STR,  OFFSET(SIT_ListBox, sl_ColumnWidths) },
		{ SIT_ColumnNames,   "columnNames",   _SG, SIT_STR,  OFFSET(SIT_ListBox, sl_ColumnNames) },
		{ SIT_ColumnAlign,   "columnAlign",   _SG, SIT_STR,  OFFSET(SIT_ListBox, sl_ColumnAlign) },
		{ SIT_SelectedIndex, "selectedIndex", _SG, SIT_INT,  OFFSET(SIT_ListBox, sl_SelIndex) },
		{ SIT_ItemCount,     NULL,            __G, SIT_INT,  0 },
		{ SIT_ItemPerPage,   NULL,            __G, SIT_INT,  0 },
		{ SIT_MakeVisible,   "makeVisible",   _S_, SIT_INT,  OFFSET(SIT_ListBox, sl_CurRow) },
		{ SIT_TargetRow,     NULL,            _SG, SIT_INT,  OFFSET(SIT_ListBox, sl_CurRow) },
		{ SIT_RowSelArg,     NULL,            _SG, SIT_BOOL, 0 },
		{ SIT_RowTagArg,     NULL,            _SG, SIT_PTR,  0 },
		{ SIT_RowTextArg,    NULL,            _SG, SIT_PTR,  0 },
		{ SIT_SortColumn,    "sortColumn",    _SG, SIT_INT,  OFFSET(SIT_ListBox, sl_SortColumn) },
		{ SIT_BgPattern,     "bgPattern",     _SG, SIT_PTR,  OFFSET(SIT_ListBox, sl_BgPattern) },
		{ SIT_BgPosY,        "bgPosY",        _SG, SIT_INT,  OFFSET(SIT_ListBox, sl_BgPos.y) },
		{ SIT_BgPosX,        "bgPosX",        _SG, SIT_INT,  OFFSET(SIT_ListBox, sl_BgPos.x) },
		{ SIT_BgRepeat,      "bgRepeat",      _SG, SIT_INT,  OFFSET(SIT_ListBox, sl_BgRepeat) },
		{ SIT_BgStyle,       "bgStyle",       _SG, SIT_ABBR, ABBR(1, 1, 1, 0) },
		{ SIT_TagEnd }
	};

#define	MAXVAL                  0x10000
#define	MAXFLAG                 (MAXVAL<<1)
#define	MAXMASK                 (MAXFLAG-1)
#define	COLNUM(x)               ((x) < -1 ? -(x)-2 : (x))
#define	ROWCOL(row, col)        (((row)<<8) | (col))
#define	ListBoxBgChanged        (1<<30)
#define	ListBoxColumnChanged    (1<<29)
#define	ListBoxSortChanged      (1<<28)
#define	ListBoxColorChanged     (1<<27)
#define	ListBoxViewChanged      (1<<26)
#define	ListBoxIndexChanged     (1<<25)
#define	NoEndTrack              (1<<30)
#define	WM_SORTITEMS            (WM_APP+2)

static int SIT_MeasureListBox(SIT_Widget w, APTR cd, APTR ud)
{
	SIZE * ret = cd;
	HWND   hdr = ListView_GetHeader(w->sw_Handle);
	RECT   rect;
	if (hdr) GetClientRect(hdr, &rect);
	else     rect.bottom = 6;
	ret->cx = 10;
	ret->cy = rect.bottom + 4;
	return 0;
}

static int SIT_ListResizeColumn(SIT_Widget w, int noresize, int newwidth)
{
	SIT_ListBox list = (APTR) w;
	LVCOLUMN col;
	int i, j, left;

	if (list->sl_CWidths == NULL) return 0;

	w->sw_Flags |= SITF_Style2Changed;
	for (i = 0, col.mask = LVCF_WIDTH, j = list->sl_ColumnCount, left = newwidth; i < list->sl_ColumnCount; i ++)
	{
		if (list->sl_CWidths[i] < MAXFLAG) {
			col.cx = list->sl_CWidths[i];
			left -= col.cx; newwidth -= col.cx;
			ListView_SetColumn(w->sw_Handle, i, &col);
			j --;
		}
	}

	for (i = 0; i < list->sl_ColumnCount; i ++)
	{
		if (list->sl_CWidths[i] & MAXFLAG)
		{
			col.cx = j <= 1 ? left : (list->sl_CWidths[i] & MAXMASK) * newwidth >> 16;
			left -= col.cx;
			if (i != noresize)
				ListView_SetColumn(w->sw_Handle, i, &col);

			j --;
		}
	}
	w->sw_Flags &= ~SITF_Style2Changed;
	return 0;
}

static void SIT_RedistributeWidths(SIT_ListBox list, int col, int newwidth)
{
	RECT rect;
	int  i, total, max, nb;

	GetClientRect(list->super.sw_Handle, &rect);
	for (i = 0; i < list->sl_ColumnCount; i ++)
		if (list->sl_CWidths[i] < MAXFLAG) rect.right -= list->sl_CWidths[i];

	list->sl_CWidths[col] = (newwidth * MAXVAL + MAXVAL/2) / rect.right | MAXFLAG;

	for (i = total = nb = max = 0; i < list->sl_ColumnCount; i ++) {
		if (list->sl_CWidths[i] < MAXFLAG) continue;
		if (i <= col)
			total += list->sl_CWidths[i] & MAXMASK;
		else
			max += list->sl_CWidths[i] & MAXMASK, nb ++;
	}

	for (i = col + 1, col = MAXVAL - total; i < list->sl_ColumnCount; i ++) {
		if (list->sl_CWidths[i] & MAXFLAG)
		{
			list->sl_CWidths[i] = col < 0 ? 0 : nb == 1 ? MAXVAL - total : max <= 0 ? 0 :
			                      (int) ((list->sl_CWidths[i] & MAXMASK) * (uint64_t) col / max);
			total += list->sl_CWidths[i];
			list->sl_CWidths[i] |= MAXFLAG; nb --;
		}
	}
	#if	0
	fprintf(stderr, "cols = ");
	for (i = 0; i < list->sl_ColumnCount; i ++)
		fprintf(stderr, "%x%c", list->sl_CWidths[i] & MAXMASK, i < list->sl_ColumnCount-1 ? ',' : '\n');
	#endif
}

/* Case insensitive alpha num sort, inspired by Dirk Jagdmann C++ version of alphanum.hpp */
static int wcsalnum(LPWSTR str1, LPWSTR str2)
{
	while (*str1 && *str2)
	{
		int iswd1 = iswdigit(*str1);
		int iswd2 = iswdigit(*str2);
		if (iswd1 && iswd2)
		{
			ULONG n1 = wcstoul(str1, &str1, 10); /* Force decimal, do not use 0 for base */
			ULONG n2 = wcstoul(str2, &str2, 10);
			if (n1 < n2) return -1;
			if (n1 > n2) return  1;
		}
		else if (iswd1)
		{
			return -1;
		}
		else if (iswd2)
		{
			return 1;
		}
		else /* Case insensitive compare */
		{
			WCHAR chr[4] = {*str1, 0, *str2};
			int   res = wcsicmp(chr, chr + 2);
			if (res) return res;
			str1 ++;
			str2 ++;
		}
	}
	if (*str2) return -1;
	if (*str1) return  1;
	return 0;
}

#define	list         ((SIT_ListBox)w)

/* Comparison hook */
static int CALLBACK SIT_ListCompareHook(LPARAM index1, LPARAM index2, LPARAM ud)
{
	WCHAR      text[256*2];
	SIT_OnSort msg;
	SIT_Widget w    = (APTR) ud;
	LVITEM     item = {.mask = LVIF_PARAM, .cchTextMax = DIM(text)/2, .pszText = text};
	int        ret  = SIT_SortAlpha;

	msg.column = COLNUM(list->sl_SortColumn);
	item.iItem = index1; ListView_GetItem(w->sw_Handle, &item); msg.item1 = (APTR) item.lParam;
	item.iItem = index2; ListView_GetItem(w->sw_Handle, &item); msg.item2 = (APTR) item.lParam;

	if (HAS_EVT(w, SITE_OnSortItem))
	{
		SIT_Callback cb;

		for (cb = HEAD(w->sw_Callbacks); cb && cb->sc_Event != SITE_OnSortItem; NEXT(cb));
		ret = cb->sc_CB(w, &msg, cb->sc_UserData);
	}
	if (ret > 1)
	{
		/* Get item's text */
		item.iSubItem = msg.column;
		item.mask = LVIF_TEXT;
		item.iItem = index1; ListView_GetItem(w->sw_Handle, &item); item.pszText += 256;
		item.iItem = index2; ListView_GetItem(w->sw_Handle, &item);
		switch (ret) {
		case SIT_SortAlpha:    ret = wcsicmp(text, text + 256); break;
		case SIT_SortBinary:   ret = wcscmp(text, text + 256); break;
		case SIT_SortNumeric:  ret = wcstol(text, NULL, 10) - wcstol(text+256, NULL, 10); break;
		case SIT_SortAlphaNum: ret = wcsalnum(text, text + 256); break;
		}
	}
	return list->sl_SortColumn < -1 ? -ret : ret;
}

static int SIT_SetListStyles(SIT_Widget w, APTR cd, APTR ud)
{
	/* Watermark/background image */
	if (list->sl_Flags & ListBoxBgChanged)
	{
		LVBKIMAGE bk = {.ulFlags = LVBKIF_SOURCE_HBITMAP};
		bk.xOffsetPercent = list->sl_BgPos.x;
		bk.yOffsetPercent = list->sl_BgPos.y;
		switch (list->sl_BgRepeat) {
		case SITV_TileWithPxOff: bk.ulFlags |= LVBKIF_FLAG_TILEOFFSET; // no break;
		case SITV_TilePattern:   bk.ulFlags |= LVBKIF_STYLE_TILE; break;
		}
		/* hbm will be managed by list-view after SendMessage */
		bk.hbm = GFX_LoadImage(list->sl_BgPattern);
		ListView_SetBkImage(w->sw_Handle, &bk);
		list->sl_Flags &= ~ListBoxBgChanged;
	}
	/* Column width/name/alignment */
	if (list->sl_Flags & ListBoxColumnChanged)
	{
		LPWSTR  hdr, next;
		HDC     hdc = GetDC(w->sw_Handle);
		HGDIOBJ old = SelectObject(hdc, w->sw_Font);
		STRPTR  wid = list->sl_ColumnWidths;
		STRPTR  cal = list->sl_ColumnAlign;
		double  max = 0, nb;
		int     i, n, offset;

		UTF8ToUTF16(list->sl_ColumnNames, hdr);
		if (! cal) cal = "";

		for (i = offset = 0; *hdr; hdr = next, i ++)
		{
			LVCOLUMN col = {.mask = LVCF_TEXT|LVCF_WIDTH|LVCF_FMT};
			SIZE sz;

			for (next = hdr; *next && *next != '\t'; next ++);
			GetTextExtentPoint32(hdc, hdr, next - hdr, &sz);
			if (*next) *next++ = 0;

			col.pszText = hdr;
			col.cx = sz.cx + 15;
			switch (*cal) {
			case 'R': case 'r': col.fmt = LVCFMT_RIGHT; break;
			case 'C': case 'c': col.fmt = LVCFMT_CENTER; break;
			}
			while (*cal && *cal != '\t') cal ++;
			if (*cal) cal ++;
			if (i >= list->sl_ColumnCount)
			{
				ListView_InsertColumn(w->sw_Handle, i, &col);
				list->sl_ColumnCount ++;
			}
			else ListView_SetColumn(w->sw_Handle, i, &col);

			if (wid)
			{
				if (*wid != '*')
				{
					nb = strtod(wid, &wid);
					if (*wid == '*') max += nb, wid ++;
					else offset += nb;
				}
				else max ++, wid ++;
				if (*wid == '\t') wid ++;
				else wid = NULL;
			}
			else if (max > 0) max ++;
		}
		if (max > 0 || offset > 0)
		{
			int * tab = list->sl_CWidths = realloc(list->sl_CWidths, sizeof (int) * i);

			for (n = 0, wid = list->sl_ColumnWidths; n < i; n ++)
			{
				if (*wid != '*' && *wid)
				{
					nb = strtod(wid, &wid);
					if (*wid == '*') tab[n] = (int)(MAXVAL * nb / max) | MAXFLAG, wid ++;
					else tab[n] = nb;
				}
				else tab[n] = (int) (MAXVAL / max) | MAXFLAG, wid ++;
				if (*wid == '\t') wid ++;
				else wid = "";
			}
		}
		else if (list->sl_CWidths)
		{
			free(list->sl_CWidths);
			list->sl_CWidths = NULL;
		}
		while (i < list->sl_ColumnCount)
			ListView_DeleteColumn(w->sw_Handle, i), list->sl_ColumnCount --;
		SelectObject(hdc, old);
		ReleaseDC(w->sw_Handle, hdc);
		list->sl_Flags &= ~ListBoxColumnChanged;
		if (list->sl_CWidths && IsWindowVisible(w->sw_Handle))
		{
			RECT rect;
			GetClientRect(w->sw_Handle, &rect);
			SIT_ListResizeColumn(w, -1, rect.right);
		}
	}
	/* Sorting column */
	if (list->sl_Flags & ListBoxSortChanged)
	{
		HDITEM item = {.mask = HDI_FORMAT, .fmt = HDF_STRING};
		HWND   hdr  = ListView_GetHeader(w->sw_Handle);
		int    csc  = COLNUM(list->sl_SortColumn);

		if (list->sl_PrevSort != csc && list->sl_PrevSort >= 0)
			Header_SetItem(hdr, list->sl_PrevSort, &item);
		if (list->sl_SortColumn != -1)
		{
			item.fmt = list->sl_SortColumn < -1 ? HDF_SORTDOWN|HDF_STRING : HDF_SORTUP|HDF_STRING;
			Header_SetItem(hdr, csc, &item);
			ListView_SortItemsEx(w->sw_Handle, SIT_ListCompareHook, (LPARAM) w);
		}
		list->sl_Flags &= ~ListBoxSortChanged;
	}
	/* Listbox colors */
	if (list->sl_Flags & ListBoxColorChanged)
	{
		ULONG rgb;
		if (w->sw_Flags & SITF_HasCustFg) {
			if (ISSYS(w->sw_Fg)) rgb = GetSysColor(sysColorsWin32[w->sw_Fg & 0Xffff]);
			else rgb = w->sw_Fg;
			ListView_SetTextColor(w->sw_Handle, list->fg = rgb);
		}
		if (w->sw_Flags & SITF_HasCustBg) {
			if (ISSYS(w->sw_Bg)) rgb = GetSysColor(sysColorsWin32[w->sw_Bg & 0Xffff]);
			else rgb = w->sw_Bg;
			list->bg = rgb;
			ListView_SetTextBkColor(w->sw_Handle, rgb);
			ListView_SetBkColor(w->sw_Handle, rgb);
		}
		list->sl_Flags &= ~ListBoxColorChanged;
	}
	/* Listbox view mode */
	if (list->sl_Flags & ListBoxViewChanged)
	{
		if (list->sl_ViewMode <= SITV_ListViewSingleCol)
		{
			char viewMode[] = {LVS_REPORT, LVS_ICON, LVS_SMALLICON, LVS_LIST};
			SetWindowLong(w->sw_Handle, GWL_STYLE, (GetWindowLong(w->sw_Handle, GWL_STYLE) & ~LVS_TYPEMASK) |
				viewMode[list->sl_ViewMode]);
		}
		else /* Tile view - only available on XP or later */
			SendMessage(w->sw_Handle, LVM_SETVIEW, LV_VIEW_TILE, 0);

		list->sl_Flags &= ~ListBoxViewChanged;
	}
	if (list->sl_Flags & ListBoxIndexChanged)
	{
		int cur = -1, sel = list->sl_SelIndex;
		LVITEM item = {.mask = LVIF_STATE, .stateMask = LVIS_SELECTED | LVIS_FOCUSED};
		while ((cur = ListView_GetNextItem(w->sw_Handle, cur, LVIS_SELECTED)) >= 0)
		{
			if (cur == sel) continue;
			item.iItem = cur;
			item.state = 0;
			ListView_SetItem(w->sw_Handle, &item);
		}
		item.state = LVIS_SELECTED | LVIS_FOCUSED;
		item.iItem = list->sl_SelIndex = sel;
		ListView_SetItem(w->sw_Handle, &item);
		list->sl_Flags &= ~ListBoxIndexChanged;
	}
	w->sw_PostProcess = NULL;
	return 0;
}

static int SIT_SetListBoxValues(SIT_Widget w, APTR cd, APTR ud)
{
	TagList *     tag   = cd;
	SIT_Variant * value = ud;
	int           isSet = 0;

	switch (tag->tl_TagID) {
	case SIT_BgPattern:    list->sl_BgPattern = value->pointer; isSet = ListBoxBgChanged; break;
	case SIT_BgPosX:       list->sl_BgPos.x   = value->integer; isSet = ListBoxBgChanged; break;
	case SIT_BgPosY:       list->sl_BgPos.y   = value->integer; isSet = ListBoxBgChanged; break;
	case SIT_BgRepeat:     list->sl_BgRepeat  = value->integer; isSet = ListBoxBgChanged; break;
	case SIT_ListBoxFlags: list->sl_Flags &= ~0xff; list->sl_Flags |= value->integer; break;
	case SIT_ColumnNames:
	case SIT_ColumnWidths:
	case SIT_ColumnAlign:  SIT_SetWidgetValue(w, cd, ud); isSet = ListBoxColumnChanged; break;
	case SIT_Foreground:
	case SIT_Background:   SIT_SetWidgetValue(w, cd, ud); isSet = ListBoxColorChanged; break;
	case SIT_ViewMode:     list->sl_ViewMode = value->integer; isSet = ListBoxViewChanged; break;
	case SIT_RowTagArg: {
	 	LVITEM item = {.mask = LVIF_PARAM, .lParam = (LPARAM) value->pointer, .iItem = list->sl_CurRow};
		/* Don't care if handle is not yet valid */
		ListView_SetItem(w->sw_Handle, &item);
	}	break;
	case SIT_RowSelArg: {
		LVITEM item = {.mask = LVIF_STATE, .iItem = list->sl_CurRow, .stateMask = LVIS_SELECTED};
		if (value->boolean) item.state = LVIS_SELECTED;
		ListView_SetItem(w->sw_Handle, &item);
	}	break;
	case SIT_RowTextArg: {
		LVITEM item = {.mask = LVIF_TEXT, .iItem = list->sl_CurRow & 0xffffff, .iSubItem = list->sl_CurRow>>24};
		UTF8ToUTF16(value->pointer, item.pszText);
		ListView_SetItem(w->sw_Handle, &item);
	}	break;
	case SIT_ItemPerPage: // RO
	case SIT_ItemCount: return 0; // RO
	case SIT_MakeVisible:
		ListView_EnsureVisible(w->sw_Handle, list->sl_CurRow, FALSE);
		break;
	case SIT_SelectedIndex:
		list->sl_SelIndex = value->integer; isSet |= ListBoxIndexChanged;
		break;
	case SIT_SortColumn:
		list->sl_PrevSort = COLNUM(list->sl_SortColumn);
		list->sl_SortColumn = value->integer;
		isSet |= ListBoxSortChanged;
		break;
	default: return SIT_SetWidgetValue(w, cd, ud); // super class
	}
	if (isSet)
	{
		list->sl_Flags |= isSet;
		w->sw_PostProcess = SIT_SetListStyles;
	}
	return 0;
}

static LRESULT CALLBACK SIT_ListSubClassHandler(HWND wnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	SIT_Widget w = GetPropW(wnd, sit_class);

	switch (message) {
	case WM_WINDOWPOSCHANGING: {
		WINDOWPOS * pos = (APTR) lParam;
		if (! (pos->flags & SWP_NOSIZE))
		{
			RECT rect, client;
			GetWindowRect(wnd, &rect);
			GetClientRect(wnd, &client);
			rect.right -= rect.left;
			list->sl_NCWidth = rect.right - client.right;
			if (rect.right > pos->cx)
				SIT_ListResizeColumn(w, -1, pos->cx - list->sl_NCWidth);
			else
				w->sw_Flags |= SITF_ListEvt;
		}
		return CallWindowProc(w->sw_SuperProc, wnd, message, wParam, lParam);
	}
	case WM_WINDOWPOSCHANGED: {
		WINDOWPOS * pos = (APTR) lParam;
		RECT rect;
		GetClientRect(wnd, &rect);
		if (pos->cx - rect.right != list->sl_NCWidth)
		{
			/*
			 * Vertical scroll bar is about to appear/disappear: need the operation
			 * to be completed before we can start resizing columns.
			 */
			list->sl_NCWidth = pos->cx - rect.right;
			PostMessageW(wnd, WM_APP, 0, 0);
			w->sw_Flags &= ~SITF_ListEvt;
		}
		else if (! (pos->flags & SWP_NOSIZE) && (w->sw_Flags & SITF_ListEvt))
		{
			list->sl_NCWidth = pos->cx - rect.right;
			SIT_ListResizeColumn(w, -1, rect.right);
			w->sw_Flags &= ~SITF_ListEvt;
		}
		return CallWindowProc(w->sw_SuperProc, wnd, message, wParam, lParam);
	}
	case WM_APP: {
		RECT rect;
		GetClientRect(wnd, &rect);
		SIT_ListResizeColumn(w, -1, rect.right);
		return 0;
	}
	case WM_APP+1: /* What a PITA to just track item selection change. Sigh */
		w->sw_Flags &= ~SITF_ListEvt;
		SIT_ApplyCallback(w, (APTR) ListView_GetNextItem(wnd, -1, LVNI_SELECTED),
			HAS_EVT(w, SITE_OnChanged) ? SITE_OnChanged : SITE_OnChange);
		return 0;
	case WM_SORTITEMS:
		list->sl_Flags &= ~ListBoxSortChanged;
		ListView_SortItemsEx(w->sw_Handle, SIT_ListCompareHook, (LPARAM) w);
		return 0;
	case WM_GETDLGCODE:
		if (SIT_SubClassHandler(wnd, message, wParam, lParam) == DLGC_WANTMESSAGE)
			return DLGC_WANTMESSAGE;
		return CallWindowProc(w->sw_SuperProc, wnd, message, wParam, lParam);
	case WM_NOTIFY: {
		LPNMHEADER nmh = (APTR) lParam;

		switch (nmh->hdr.code) {
		case HDN_BEGINTRACK:
		case HDN_DIVIDERDBLCLICK:
			if (list->sl_ColumnWidths == NULL) break;
			if (list->sl_CWidths[nmh->iItem] < MAXVAL || nmh->iItem == list->sl_ColumnCount-1)
				return TRUE; // Cancel :-/
			list->sl_DivTrack = nmh->iItem;
			if (nmh->hdr.code == HDN_DIVIDERDBLCLICK)
				list->sl_Flags |= NoEndTrack;
			break;
		case HDN_ITEMCLICK:
		/* HDF_SORTUP and HDF_SORTDOWN is only supported on comctl v6+ */
		{	HDITEM item = {.mask = HDI_FORMAT, .fmt = HDF_STRING};
			int    num  = COLNUM(list->sl_SortColumn);
			if (num != nmh->iItem && num >= 0)
				Header_SetItem(nmh->hdr.hwndFrom, num, &item), list->sl_SortColumn = -1;
			if (list->sl_SortColumn <= -1)
				item.fmt = HDF_SORTUP, list->sl_SortColumn = nmh->iItem;
			else
				item.fmt = HDF_SORTDOWN, list->sl_SortColumn = -nmh->iItem-2;

			item.fmt |= HDF_STRING;
			Header_SetItem(nmh->hdr.hwndFrom, nmh->iItem, &item);
			ListView_SortItemsEx(w->sw_Handle, SIT_ListCompareHook, (LPARAM) w);
		}	break;
		case HDN_ENDTRACK:    list->sl_DivTrack = -1; break;
		case HDN_ITEMCHANGED: if (list->sl_CancelEvt) { list->sl_CancelEvt = False; break; }
		case HDN_ITEMCHANGING:
			if (list->sl_DivTrack == nmh->iItem && ! (w->sw_Flags & SITF_ListEvt) && (nmh->pitem->mask & HDI_WIDTH))
			{
				HDITEM * item = nmh->pitem;
				HDITEM   cur  = {.mask = HDI_WIDTH};
				RECT     rect;

				if (nmh->iItem == list->sl_ColumnCount-1 && !(w->sw_Flags & SITF_Style2Changed))
					return TRUE;

				Header_GetItem(nmh->hdr.hwndFrom, nmh->iItem, &cur);
				if (nmh->hdr.code == HDN_ITEMCHANGING) {
					if (item->cxy > cur.cxy) list->sl_CancelEvt = True; else break;
				}

				w->sw_Flags |= SITF_ListEvt;
				GetClientRect(w->sw_Handle, &rect);
				SIT_RedistributeWidths(list, nmh->iItem, item->cxy);
				SIT_ListResizeColumn(w, nmh->iItem, rect.right);
				w->sw_Flags &= ~SITF_ListEvt;
				if (list->sl_Flags & NoEndTrack)
					list->sl_Flags &= ~NoEndTrack, list->sl_DivTrack = -1;
			}
		}
	}	// no break;
	default:
		return CallWindowProc(w->sw_SuperProc, wnd, message, wParam, lParam);
	}
}

int SIT_HandleListCustomDraw(SIT_Widget w, LPNMLVCUSTOMDRAW nm)
{
	GC gc;
	int ret;
	ULONG fg, bg;
	switch (nm->nmcd.dwDrawStage) {
	case CDDS_PREPAINT:
		if (! (w->sw_Flags & SITF_HasCustFg)) list->fg = GetTextColor(nm->nmcd.hdc);
		if (! (w->sw_Flags & SITF_HasCustBg)) list->bg = GetBkColor(nm->nmcd.hdc);
		return CDRF_NOTIFYITEMDRAW;
	case CDDS_ITEMPREPAINT:
		if (list->sl_ViewMode == SITV_ListViewReport)
			return CDRF_NOTIFYSUBITEMDRAW;
		// else no break;
	case CDDS_SUBITEM | CDDS_ITEMPREPAINT:
		gc = list->gc;
		if (gc == NULL) {
			gc = list->gc = malloc(sizeof *gc);
			gc->free = 0;
		}

		/* Damn it, couldn't they give that value directly in nmcd.rc ?! */
		ListView_GetSubItemRect(w->sw_Handle, nm->nmcd.dwItemSpec, nm->iSubItem, LVIR_LABEL, &nm->nmcd.rc);
		GFX_InitGC(gc, w->sw_Handle, nm->nmcd.hdc, &nm->nmcd.rc, NULL, NULL);
		GFX_SetDrMd(gc, GFX_MODEJAM2);
		gc->stage = ROWCOL(nm->nmcd.dwItemSpec, nm->iSubItem);
		/* Yay, nmcd.uItemState & CDIS_SELECTED does not work. Sigh */
		if (ListView_GetItemState(w->sw_Handle, nm->nmcd.dwItemSpec, LVIS_SELECTED)) {
			/* Already setup correct colors */
			GFX_SetSysColor(gc, GFX_ColorFgHighlight, False);
			GFX_SetSysColor(gc, GFX_ColorBgHighlight, True);
			gc->stage |= (1 << 31);
		}
		else
		{
			GFX_SetTextColor(gc, list->fg);
			GFX_SetBgColor(gc, list->bg);
		}
		fg = gc->fgcolor;
		bg = gc->bgcolor;
		if (SIT_ApplyCallback(w, gc, SITE_OnPaint)) {
			GFX_Free(gc);
			return CDRF_SKIPDEFAULT;
		}

		ret = 0;
		if (gc->fgcolor != fg) nm->clrText = gc->fgcolor, ret = CDRF_NEWFONT;
		if (gc->bgcolor != bg) nm->clrTextBk = gc->bgcolor, ret = CDRF_NEWFONT;
		if (gc->font) ret = CDRF_NEWFONT;

		return CDRF_NOTIFYSUBITEMDRAW | CDRF_NOTIFYPOSTPAINT | ret;

	case CDDS_SUBITEM | CDDS_ITEMPOSTPAINT:
		GFX_Free(list->gc); // Remove custom font and/or brush
	}
	return CDRF_DODEFAULT;
}

static int SIT_FinalizeListBox(SIT_Widget w, APTR cd, APTR ud)
{
	if (list->sl_ColumnNames)  free(list->sl_ColumnNames);
	if (list->sl_ColumnWidths) free(list->sl_ColumnWidths);
	if (list->sl_ColumnAlign)  free(list->sl_ColumnAlign);
	if (list->sl_CWidths)      free(list->sl_CWidths);
	if (list->sl_CurItem)      free(list->sl_CurItem);
	if (list->gc)              free(list->gc);
	return 0;
}
#undef list

BOOL SIT_CreateListBox(SIT_Widget w, va_list args)
{
	SIT_ListBox list = (SIT_ListBox) w;
	HWND        hwnd;
	int         styles = LVS_SINGLESEL;

	/* Default values */
	list->sl_SelIndex  = list->sl_DivTrack = list->sl_SortColumn = -1;
	w->sw_OptimalWidth = SIT_MeasureListBox;
	w->sw_Finalize     = SIT_FinalizeListBox;
	w->sw_SetValue     = SIT_SetListBoxValues;

	SIT_ParseTags(w, args, w->sw_Class = ListBoxClass);

	if (list->sl_Flags & SITV_SelectMultiple) styles  = 0;
	if (list->sl_Flags & SITV_NoHeaders)      styles |= LVS_NOCOLUMNHEADER;
	if (list->sl_Flags & SITV_NoSort)         styles |= LVS_NOSORTHEADER;
	if (IsVisible(w)) styles |= WS_VISIBLE;
	if (HAS_TAB(w))   styles |= WS_TABSTOP;

	/* Create the Win32 control */
	hwnd = w->sw_Handle = CreateWindowEx(WS_EX_CLIENTEDGE, WC_LISTVIEW, w->sw_Name,
		WS_CHILD | LVS_REPORT | LVS_SHOWSELALWAYS | styles,
		0, 0, 10, 10, SIT_GetContainer(w),
		(HMENU) MAKE_ID(w), instance, NULL);

	if (hwnd == NULL) return FALSE;
	SendMessage(hwnd, CCM_SETVERSION, 5, 0);
	SIT_SetDefaultFont(w);

	styles = 0;
	if (list->sl_Flags & SITV_ListWithCheckBox) styles |= LVS_EX_CHECKBOXES;
	if (list->sl_Flags & SITV_FullRowSelect)    styles |= LVS_EX_FULLROWSELECT;
	if (styles) ListView_SetExtendedListViewStyle(hwnd, styles | ListView_GetExtendedListViewStyle(hwnd));

	if (w->sw_PostProcess)
		w->sw_PostProcess(w, NULL, NULL);

	/* Keep track of class inside callback */
	SetProp(hwnd, sit_class, (HANDLE) w);
	w->sw_SuperProc = (APTR) GetWindowLong(w->sw_Handle, GWL_WNDPROC);
	SetWindowLong(w->sw_Handle, GWL_WNDPROC, (LONG) SIT_ListSubClassHandler);

	return TRUE;
}

#define	list               ((SIT_ListBox)w)
#define	IsCached(image)    (((ULONG)image & 0xffff0000) == 0xffff0000)
#define	CacheNum(image)    (((ULONG)image & 0xffff) - 1)
DLLIMP int SIT_ListInsertItem(SIT_Widget w, int row, APTR rowTag, Image image, ...)
{
	LVITEM  item = {.mask = LVIF_TEXT|LVIF_PARAM};
	STRPTR  utf8;
	va_list args;
	int     i;

	if (w == NULL) return -1;
	if (row < 0)
		row = ListView_GetItemCount(w->sw_Handle);

	if (image && ! list->sl_ImgList) {
		/* Will be freed by listview XXX weird bug with ICLC_COLOR24 and button icon. Need testing */
		list->sl_ImgList = pImageList_Create(image->width, image->height,
			image->bpp <= 24 ? ILC_COLORDDB : ILC_COLOR32, 16, 16);
		// XXX Uses a different set according to current view mode
		(void) ListView_SetImageList(w->sw_Handle, list->sl_ImgList, LVSIL_SMALL);
	}
	if (IsCached(image)) {
		item.iImage = CacheNum(image);
		item.mask |= LVIF_IMAGE;
	} else if (image) {
		item.iImage = pImageList_Add(list->sl_ImgList, image->hbitmap, NULL);
		item.mask |= LVIF_IMAGE;
	}
	item.lParam = (LPARAM) rowTag;

	for (item.iItem = row, i = 0, va_start(args, image); (utf8 = va_arg(args, STRPTR)) &&
	     i < list->sl_ColumnCount; i ++)
	{
		UTF8ToUTF16(utf8, item.pszText);
		item.iSubItem = i;
		SendMessage(w->sw_Handle, i > 0 ? LVM_SETITEM : LVM_INSERTITEM, 0, (LPARAM) &item);
		item.mask &= ~(LVIF_IMAGE|LVIF_PARAM);
	}
	va_end(args);
	if (list->sl_SortColumn != -1 && !(list->sl_Flags & ListBoxSortChanged)) {
		list->sl_Flags |= ListBoxSortChanged;
		PostMessage(w->sw_Handle, WM_SORTITEMS, 0, 0);
	}
	return row;
}

DLLIMP void SIT_ListDeleteRow(SIT_Widget w, int row)
{
	if (row == -1)
		row = ListView_GetItemCount(w->sw_Handle)-1;

	if (row == DeleteAllRow)
	{
		if (list->sl_ImgList)
			pImageList_Remove(list->sl_ImgList, -1);
		ListView_DeleteAllItems(w->sw_Handle);
	}
	else ListView_DeleteItem(w->sw_Handle, row);
}

DLLIMP void SIT_ListSetCell(SIT_Widget w, int row, int col, STRPTR text, Image image, APTR rowTag)
{
	LVITEM item = {};

	item.iItem = (row < 0 ? ListView_GetItemCount(w->sw_Handle)-1 : row);

	if (item.iItem < 0) return;

	if (rowTag != DontChangePtr) item.mask |= LVIF_PARAM;
	if (image != DontChangePtr)
	{
		item.mask |= LVIF_IMAGE;
		item.iImage = -1;
		if (image)
		{
			// XXX remove icon / create image list if required
			ListView_GetItem(w->sw_Handle, &item);
			if (item.iImage >= 0)
				pImageList_Replace(list->sl_ImgList, item.iImage, image->hbitmap, NULL);
			else
				item.iImage = pImageList_Add(list->sl_ImgList, image->hbitmap, NULL);
		}
	}

	item.lParam = (LPARAM) rowTag;

	if (col > 0 && item.mask)
		ListView_SetItem(w->sw_Handle, &item);

	if (text != DontChangePtr)
	{
		item.mask |= LVIF_TEXT;
		UTF8ToUTF16(text, item.pszText);
		item.iSubItem = col;
		if (col == COLNUM(list->sl_SortColumn) && !(list->sl_Flags & ListBoxSortChanged)) {
			list->sl_Flags |= ListBoxSortChanged;
			PostMessage(w->sw_Handle, WM_SORTITEMS, 0, 0);
		}
	}
	if (item.mask)
		ListView_SetItem(w->sw_Handle, &item);
}

DLLIMP Bool SIT_ListSetColumn(SIT_Widget w, int col, int width, int align, STRPTR label)
{
	LVCOLUMN lvc = {};

	if (col >= list->sl_ColumnCount || w == NULL) return False;

	if (width == AutoArrange)
	{
		ListView_SetColumnWidth(w->sw_Handle, col, col < list->sl_ColumnCount ? LVSCW_AUTOSIZE :
			LVSCW_AUTOSIZE_USEHEADER);
	}
	else if (width != DontChange)
	{
		lvc.mask |= LVCF_WIDTH;
		lvc.cx    = width;
	}
	if (align != DontChange)
	{
		lvc.mask |= LVCF_FMT;
		lvc.fmt   = align == 'R' ? LVCFMT_RIGHT :
		            align == 'C' ? LVCFMT_CENTER : LVCFMT_LEFT;
	}
	if (label != DontChangePtr)
	{
		UTF8ToUTF16(label, lvc.pszText);
		lvc.mask |= LVCF_TEXT;
	}
	if (lvc.mask) ListView_SetColumn(w->sw_Handle, col, &lvc);

	if (width != DontChange)
	{
		RECT rect;
		lvc.mask = LVCF_WIDTH;
		ListView_GetColumn(w->sw_Handle, col, &lvc);
		GetClientRect(w->sw_Handle, &rect);
		if (list->sl_CWidths[col] < MAXFLAG) list->sl_CWidths[col] = lvc.cx;
		else SIT_RedistributeWidths(list, col, lvc.cx);
		SIT_ListResizeColumn(w, col, rect.right);
	}
	return True;
}

#undef list
#undef COLNUM
